<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>360度弹簧物理效果组件（手感优化版）</title>
  <style>
    body { height: 100vh; margin: 0; background: #f8f8f8; display: flex; justify-content: center; align-items: center;}
    .container { position: relative; width: 480px; height: 480px; }
    #spring-ball {
      width: 70px; height: 70px; background: #4fc08d;
      border-radius: 50%; position: absolute; left: 205px; top: 55px;
      cursor: grab; box-shadow: 0 8px 24px rgba(0,0,0,0.18);
      user-select: none; z-index: 10;
      display: flex; justify-content: center; align-items: center;
      font-weight: bold; color: white; font-size: 1.1em;
      transition: box-shadow 0.2s;
    }
    #spring-ball:active { box-shadow: 0 4px 12px rgba(0,0,0,0.14);}
    #spring-svg { position: absolute; left: 0; top: 0; pointer-events: none;}
    .base {
      width: 100px; height: 18px; background: #bbb;
      border-radius: 9px; position: absolute; left: 190px; top: 400px; z-index: 2;
      box-shadow: 0 2px 10px rgba(0,0,0,0.08);
    }
  </style>
</head>
<body>
  <div class="container">
    <svg id="spring-svg" width="480" height="480"></svg>
    <div id="spring-ball">物体</div>
    <div class="base"></div>
  </div>
  <script>
    // 优化物理参数
    const stiffness = 0.9, damping = 0.96;
    const rest = { x: 205, y: 300 }; // 球静止点
    const base = { x: 240, y: 409 }; // 基座中心
    const radius = 400; // 拖拽最大距离
    let ball = document.getElementById('spring-ball');
    let svg = document.getElementById('spring-svg');
    let dragging = false, offset = {x:0, y:0}, pos = {...rest}, vx = 0, vy = 0, animating = false;

    // 绘制弹簧
    function drawSpring(p) {
      const bx = base.x, by = base.y;
      const tx = p.x + 35, ty = p.y + 35; // 球中心
      const turns = 7;
      const amp = 18;
      let dx = bx - tx, dy = by - ty;
      let len = Math.sqrt(dx*dx+dy*dy);
      let angle = Math.atan2(dy, dx);
      let points = [];
      for(let i=0;i<=turns*2;++i){
        let t = i/(turns*2);
        let px = tx + dx * t;
        let py = ty + dy * t;
        let perpAngle = angle + Math.PI/2;
        let offset = (i%2==0?-amp:amp);
        px += Math.cos(perpAngle) * offset * (1-Math.abs(t-0.5)*2);
        py += Math.sin(perpAngle) * offset * (1-Math.abs(t-0.5)*2);
        points.push([px, py]);
      }
      let d = `M${tx},${ty}`;
      points.forEach(([x, y]) => d += ` L${x},${y}`);
      d += ` L${bx},${by}`;
      svg.innerHTML = `<path d="${d}" stroke="#888" stroke-width="4" fill="none" />`;
    }

    // 初始渲染
    ball.style.left = rest.x + "px";
    ball.style.top  = rest.y + "px";
    drawSpring(rest);

    ball.addEventListener('mousedown', e => {
      dragging = true;
      offset.x = e.clientX - parseFloat(ball.style.left);
      offset.y = e.clientY - parseFloat(ball.style.top);
      ball.style.cursor = 'grabbing';
      document.body.style.userSelect = 'none';
      animating = false;
    });

    document.addEventListener('mousemove', e => {
      if (!dragging) return;
      let nx = e.clientX - offset.x;
      let ny = e.clientY - offset.y;
      let cx = nx + 35, cy = ny + 35;
      let dx = cx - base.x, dy = cy - base.y;
      let dist = Math.sqrt(dx*dx + dy*dy);
      if(dist > radius){
        dx = dx * radius / dist;
        dy = dy * radius / dist;
        cx = base.x + dx;
        cy = base.y + dy;
        nx = cx - 35; ny = cy - 35;
      }
      pos = {x: nx, y: ny};
      ball.style.left = nx + "px";
      ball.style.top  = ny + "px";
      drawSpring({x: nx, y: ny});
    });

    document.addEventListener('mouseup', e => {
      if (dragging) {
        dragging = false;
        ball.style.cursor = 'grab';
        document.body.style.userSelect = '';
        let dx = parseFloat(ball.style.left) - rest.x;
        let dy = parseFloat(ball.style.top) - rest.y;
        vx = dx * 0.17;
        vy = dy * 0.17;
        animating = true;
        requestAnimationFrame(springBack);
      }
    });

    function springBack() {
      if (!animating) return;
      let x = parseFloat(ball.style.left), y = parseFloat(ball.style.top);
      let fx = -stiffness * (x - rest.x);
      let fy = -stiffness * (y - rest.y);
      vx += fx;
      vy += fy;
      vx *= damping;
      vy *= damping;
      x += vx;
      y += vy;
      let cx = x + 35, cy = y + 35;
      let dx = cx - base.x, dy = cy - base.y;
      let dist = Math.sqrt(dx*dx + dy*dy);
      if(dist > radius){
        dx = dx * radius / dist;
        dy = dy * radius / dist;
        cx = base.x + dx;
        cy = base.y + dy;
        x = cx - 35; y = cy - 35;
        vx = 0; vy = 0;
      }
      ball.style.left = x + "px";
      ball.style.top  = y + "px";
      drawSpring({x, y});
      // 优化回弹终止判定
      if (Math.abs(vx) < 0.35 && Math.abs(vy) < 0.35 && Math.abs(x - rest.x) < 0.7 && Math.abs(y - rest.y) < 0.7) {
        ball.style.left = rest.x + "px";
        ball.style.top  = rest.y + "px";
        drawSpring(rest);
        animating = false;
        return;
      }
      requestAnimationFrame(springBack);
    }
  </script>
</body>
</html>